package com.zxy.demo.delay.pojo;

import java.io.Serializable;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

import com.zxy.demo.delay.lock.RedisLock;

/**
 * 延时任务抽象类
 * @author ZENG.XIAO.YAN
 * @date   2018年6月6日
 */
public abstract  class BaseDelayTask implements Delayed, Runnable, Serializable {
	
	/** serialVersionUID */
	private static final long serialVersionUID = 297477683618204579L;
	/** 任务id */
	protected Long id;
    /** 执行任务的开始时间 */
	protected Long startTime;
	
	/** setter and getter method */
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Long getStartTime() {
		return startTime;
	}
	
	public void setStartTime(Long startTime) {
		this.startTime = startTime;
	}

	/**
	 * 设置延时时间，单位为分钟
	 * @param minute
	 */
	public void setDelayTime(int minute) {
		this.startTime = System.currentTimeMillis() + 1000 * 60 * minute;
	}
	
	/**
	 * 设置延时时间，单位为秒
	 * @param sec
	 */
	public void setDelayTimeWithSecond(int sec) {
		this.startTime = System.currentTimeMillis() + 1000 * sec;
	}
	
	
	
	@Override
	public int compareTo(Delayed other) {
		if (other == this) {
			return 0;
		}
		if (other instanceof BaseDelayTask) {
			BaseDelayTask otherTask = (BaseDelayTask) other;
			long diff = this.startTime - otherTask.getStartTime();
			if (diff > 0) {
				return 1;
			} else if (diff < 0) {
				return -1;
			} else if(this.getId() < otherTask.getId()) {
				// 如果startTime一样的话，谁的id小，谁排在前面
				return -1;
			} else if (this.getId() > otherTask.getId()) {
				return 1;
			} else {
				return 0;
			}
		}
		return 0;
	}
	
	/**
     * 剩余时间=到期时间-当前时间
     */
	@Override
	public long getDelay(TimeUnit unit) {
		return unit.convert(this.getStartTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
	}

	@Override
	public String toString() {
		return "BaseDelayTask [id=" + id + ", startTime=" + startTime + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((startTime == null) ? 0 : startTime.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		BaseDelayTask other = (BaseDelayTask) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		if (startTime == null) {
			if (other.startTime != null)
				return false;
		} else if (!startTime.equals(other.startTime))
			return false;
		return true;
	}

	
	@Override
	public void run() {
		/**
		 * 流程说明
		 * 1.校验分布式锁，防止2台机器同时执行一个任务
		 * 2.检查redis中的任务是否存在，（防止分布式锁过期后，任务再次执行。）
		 * 		为何要检查？一般任务执行完毕后，都会把redis的中任务删除；但这个任务可能由于服务器重启，造成共享在其他服务器的内存队列中。
		 * 3.开始执行任务
		 * 4.任务顺利执行，删除redis中的任务
		 * 5.最终都要释放锁
		 * */
		
		// 分布式锁生存时间先设置为5分钟，一般够用了
		if(!RedisLock.islock(lockKey(), 5 * 60)) {
			if(checkTaskExistOnRedis()) {
				try {
					// 先执行任务
					runTask();
					// 执行完任务后，删除Redis中存的任务
					deleteTaskOnRedis();
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					// 释放锁
					RedisLock.delLock(lockKey());
				}
			}
		}
		
	}
	
	/**
	 * 执行任务方法，由子类重写
	 */
	public abstract void runTask(); 
	
	/**
	 * 执行删除Redis中任务方法，由子类重写
	 */
	public abstract void deleteTaskOnRedis(); 
	
	/**
	 * 检查任务是否还在Redis中，由子类重写
	 * @see 集群部署后，当其中一台服务器重启时，会把所有的redis中的任务都加载到延时队列中。
	 * <br/>有部分任务被其他服务器消费后，会从redis中删除，但这个台重启后的机器的队列中还有该任务。
	 * <br/>所以在执行任务前先判断下这个任务在redis中是否存在。
	 * 
	 * @return 存在 true，不存在 false
	 */
	public abstract boolean checkTaskExistOnRedis();

	/**
	 * redis分布式锁的key，由子类重写
	 * @return key
	 */
	public abstract String lockKey();
	
}
